package engine.entities;

import engine.entities.MobClass.MobClass;
import engine.entities.attribute.MobStatSet;
import engine.entities.inventory.item.MobEquipSet;
import engine.game.combat.Combat;
import java.awt.Point;

import engine.game.gfx.ColoredSprite;
import engine.game.gfx.Screen;
import engine.game.hud.StatBlockViewer.DispProp;
import engine.game.hud.StatBlockViewer.ViewerAction;
import engine.game.level.Level;

public abstract class Mob extends Entity {

    protected double speed;
    protected boolean isMoving;
    protected DIRECTION movingDir;
    private DIRECTION facing;
    protected int moveTickCounter = 0;
    protected boolean moving = false;

    protected long timeOfMovementStart;
    
    protected boolean doingAnim;
    
    private ColoredSprite activeSprite;

    private ColoredSprite frontFaceSprite;
    private ColoredSprite leftFaceSprite;
    private ColoredSprite backFaceSprite;
    private ColoredSprite rightFaceSprite;

    private ColoredSprite frontWalkSprite1;
    private ColoredSprite frontWalkSprite2;
    private ColoredSprite leftWalkSprite;
    private ColoredSprite backWalkSprite1;
    private ColoredSprite backWalkSprite2;
    private ColoredSprite rightWalkSprite;

    private ColoredSprite lastFrontWalkSprite;
    private ColoredSprite lastBackWalkSprite;

    private ColoredSprite rightAttackSprite;
    private ColoredSprite leftAttackSprite;
    private ColoredSprite backAttackSprite;
    private ColoredSprite frontAttackSprite;
    
    private ColoredSprite castSprite;
    private ColoredSprite deadSprite;

    protected int xa;
    protected int ya;

    protected MobResource health;
    protected MobResource stamina;
    protected MobResource mana;
    protected MobResource aether;
    protected MobStatSet stats;

    protected MobClass mobClass;

    protected boolean inCombat = false;
    protected boolean isDead = false;
    protected Combat combat;
    protected boolean combatTurnOver = true;
    protected boolean attacking = false;
    protected long attackStartTime;
    protected Mob attackTarget;

    protected Point moveStart;
    
    protected MobEquipSet equipSet;

    public Mob(Level level, String name, MobClass mobClass, MobStatSet stats, MobEquipSet equipSet) {
        super(name);
        this.level = level;
        this.name = name;
        this.mobClass = mobClass;
        this.stats = stats;
        this.facing = DIRECTION.DOWN;
        this.equipSet = equipSet;

        loadDefaultResources();

        loadSprites(mobClass.getSpriteSet());
    }

    protected final void loadSprites(MobSpriteSet set) {
        frontFaceSprite = set.getFrontFaceSprite();
        leftFaceSprite = set.getLeftFaceSprite();
        backFaceSprite = set.getBackFaceSprite();
        rightFaceSprite = set.getRightFaceSprite();

        frontWalkSprite1 = set.getFrontWalkSprite1();
        frontWalkSprite2 = set.getFrontWalkSprite2();

        backWalkSprite1 = set.getBackWalkSprite1();
        backWalkSprite2 = set.getBackWalkSprite2();

        rightWalkSprite = set.getRightWalkSprite();
        leftWalkSprite = set.getLeftWalkSprite();
        
        rightAttackSprite = set.getRightAttackSprite();
        leftAttackSprite = set.getLeftAttackSprite();
        backAttackSprite = set.getBackAttackSprite();
        frontAttackSprite = set.getFrontAttackSprite();
        
        castSprite = set.getCastSprite();
        deadSprite = set.getDeadSprite();

        activeSprite = frontFaceSprite;
        facing = DIRECTION.DOWN;
    }

    protected final void loadDefaultResources() {
        this.health = new MobResource("HEALTH", mobClass.getHealth() + stats.getVigor().getModifier() * mobClass.getLevel());
        this.stamina = new MobResource("STAMINA", mobClass.getStamina() + stats.getStrength().getModifier() * mobClass.getLevel());
        this.mana = new MobResource("MANA", mobClass.getMana() + (stats.getIntelligence().getModifier() + stats.getWillpower().getModifier()) * mobClass.getLevel());
        this.aether = new MobResource("AETHER", mobClass.getAether() + (stats.getMetamagic().getModifier() + stats.getCharisma().getModifier()) * mobClass.getLevel());
    }

    public void move(int xa, int ya) {
        if ((xa != 0) && (ya != 0)) {
            move(xa, 0);
            move(0, ya);
            return;
        }
        else if (!hasCollided(xa, ya)) {
            if (ya < 0) {
                movingDir = DIRECTION.UP;
            }
            else if (ya > 0) {
                movingDir = DIRECTION.DOWN;
            }
            else if (xa < 0) {
                movingDir = DIRECTION.LEFT;
            }
            else if (xa > 0) {
                movingDir = DIRECTION.RIGHT;
            }
            moving = true;
            startMoving(movingDir);
        }
        else {
            if (ya < 0) {
                setFacing(DIRECTION.UP);
            }
            else if (ya > 0) {
                setFacing(DIRECTION.DOWN);
            }
            else if (xa < 0) {
                setFacing(DIRECTION.LEFT);
            }
            else if (xa > 0) {
                setFacing(DIRECTION.RIGHT);
            }
        }
    }

    @Override
    public void tick() {
        if (doingAnim) {
            return;
        }
        
        if (moving && doneMoving()) {
            stopMoving();
            moving = false;
        }

        xa = 0;
        ya = 0;

        if (moving) {
            double progress = Math.abs(xOff) / ((double) level.TILE_SIZE);

            switch (movingDir) {
                case UP:
                    if (lastBackWalkSprite == null || lastBackWalkSprite == backWalkSprite2) {
                        activeSprite = backWalkSprite1;
                    }
                    else {
                        activeSprite = backWalkSprite2;
                    }
                    yOff = -1 * (int) Math.round(90 * (System.currentTimeMillis() - timeOfMovementStart) / 1E3);
                    break;
                case DOWN:
                    if (lastFrontWalkSprite == null || lastFrontWalkSprite == frontWalkSprite2) {
                        activeSprite = frontWalkSprite1;
                    }
                    else {
                        activeSprite = frontWalkSprite2;
                    }
                    yOff = (int) Math.round(90 * (System.currentTimeMillis() - timeOfMovementStart) / 1E3);
                    break;
                case LEFT:
                    if (0.25 < progress && progress < 0.75) {
                        activeSprite = leftWalkSprite;
                    }
                    else {
                        activeSprite = leftFaceSprite;
                    }
                    xOff = -1 * (int) Math.round(90 * (System.currentTimeMillis() - timeOfMovementStart) / 1E3);
                    break;
                case RIGHT:
                    if (0.25 < progress && progress < 0.75) {
                        activeSprite = rightWalkSprite;
                    }
                    else {
                        activeSprite = rightFaceSprite;
                    }
                    xOff = (int) Math.round(90 * (System.currentTimeMillis() - timeOfMovementStart) / 1E3);
                    break;
            }
        }
        
        if (attacking) {
            updateAttackStatus();
        }
    }

    @Override
    public DispProp[] getDisplayProperties() {
        DispProp[] props = new DispProp[3];
        props[0] = new DispProp("Name", this.getName());
        props[1] = new DispProp("Class", this.mobClass.getName());
        props[2] = new DispProp("Level", this.mobClass.getLevel() + "");
        return props;
    }
    
    @Override
    public ViewerAction[] getViewerActions() {
        return null;
    }
    
    @Override
    public ViewerAction getDefaultAction() {
        return null;
    }
    
    public boolean hasCollided(int xa, int ya) {
        return !canTraverse(new Point(coords.x + xa, coords.y + ya));
    }

    public boolean canTraverse(Point pos) {
        return !(level.getTile(pos.x, pos.y).isSolid() || (level.entityAt(pos) != null));
    }
    
    @Override
    public void render(Screen screen) {
        int xOffset = xOff + (this.coords.x * level.TILE_SIZE);
        int yOffset = yOff + (this.coords.y * level.TILE_SIZE);

        screen.render(xOffset, yOffset, activeSprite, SCALE);
    }
    
    @Override
    public boolean isViewable() {
        return true;
    }
    
    @Override
    public ColoredSprite getSprite() {
        return getStaticSprite();
    }

    public void setFacing(DIRECTION dir) {
        switch (dir) {
            case UP:
                activeSprite = backFaceSprite;
                break;
            case DOWN:
                activeSprite = frontFaceSprite;
                break;
            case LEFT:
                activeSprite = leftFaceSprite;
                break;
            case RIGHT:
                activeSprite = rightFaceSprite;
                break;
        }
        facing = dir;
    }

    public void startMoving(DIRECTION dir) {
        timeOfMovementStart = System.currentTimeMillis();
        moveStart = getCoords();
        switch (movingDir) {
            case UP:
                activeSprite = backFaceSprite;
                break;
            case DOWN:
                activeSprite = frontFaceSprite;
                break;
            case LEFT:
                activeSprite = leftFaceSprite;
                break;
            case RIGHT:
                activeSprite = rightFaceSprite;
                break;
        }
    }

    public void stopMoving() {
        Point oldCoords = coords;
        switch (movingDir) {
            case UP:
                lastBackWalkSprite = activeSprite;
                coords = new Point(moveStart.x, moveStart.y - 1);
                activeSprite = backFaceSprite;
                break;
            case DOWN:
                lastFrontWalkSprite = activeSprite;
                coords = new Point(moveStart.x, moveStart.y + 1);
                activeSprite = frontFaceSprite;
                break;
            case LEFT:
                coords = new Point(moveStart.x - 1, moveStart.y);
                activeSprite = leftFaceSprite;
                break;
            case RIGHT:
                coords = new Point(moveStart.x + 1, moveStart.y);
                activeSprite = rightFaceSprite;
                break;
        }
        Point newCoords = coords;
        xOff = 0;
        yOff = 0;
        if (!level.moveEntity(oldCoords, newCoords)) {
            coords = oldCoords;
        }
    }

    public boolean doneMoving() {
        return ((Math.abs(xOff) >= level.TILE_SIZE) || (Math.abs(yOff) >= level.TILE_SIZE));
    }

    public int xOff() {
        return xOff;
    }

    public int yOff() {
        return yOff;
    }

    @Override
    public int[][] pixels() {
        return activeSprite.pixels();
    }

    public ColoredSprite getStaticSprite() {
        return frontFaceSprite;
    }

    public ColoredSprite getFaceSprite() {
        switch(facing) {
            case RIGHT:
                return rightFaceSprite;
            case LEFT:
                return leftFaceSprite;
            case UP:
                return backFaceSprite;
            case DOWN:
                return frontFaceSprite;
        }
        return getStaticSprite();
    }
    
    public void takeDamage(int dmg) {
        health.setValue(Math.max(0, health.getValue() - dmg));
        doHealthCheck();
    }

    private void doHealthCheck() {
        this.isDead = deathCheck();
        if (isDead()) {
            if (combat != null) {
                combat.removeCombatant(this);
            }
            this.activeSprite = deadSprite;
        }
    }
    
    public void gainHealth(int heal) {
        this.health.setValue(Math.min(health.getMaxValue(), health.getValue() + heal));
    }
    
    public void payHealthCost(int cost) {
        health.setValue(Math.max(0, health.getValue() - cost));
        doHealthCheck();
    }
    
    public void payStaminaCost(int cost) {
        stamina.setValue(Math.max(0, stamina.getValue() - cost));
    }
    
    public void payManaCost(int cost) {
        mana.setValue(Math.max(0, mana.getValue() - cost));
    }
    
    public void payAetherCost(int cost) {
        aether.setValue(Math.max(0, aether.getValue() - cost));
    }
    
    public boolean isInCombat() {
        return inCombat;
    }

    public void setCombat(boolean combat) {
        inCombat = combat;
        if (!inCombat) {
            this.combat = null;
        }
    }

    public void setCombat(boolean inCombat, Combat combat) {
        this.inCombat = inCombat;
        this.combat = combat;
        if (!inCombat) {
            this.combat = null;
        }
    }

    private boolean deathCheck() {
        return (health.getValue() <= 0);
    }

    public boolean isDead() {
        return isDead;
    }

    public void attack(Mob other) {
        if (combat == null && !other.isInCombat()) {
            startCombat(other);
        }
        setFacing(getDirectionTowards(other));
        attackTarget = other;
        attacking = true;
        attackStartTime = System.currentTimeMillis();
    }
    
    public void startCombat(Mob other) {
        inCombat = true;
        combatTurnOver = false;
        this.combat = new Combat(this, other);
    }

    protected void completeAttack() {
        attackTarget.takeDamage(8);
        combatTurnOver = true;
    }

    protected void updateAttackStatus() {
        double delta = (System.currentTimeMillis() - attackStartTime) / 250.0;
        if (delta > 0.2 && delta < 0.8) {
            activeSprite = getAttackSprite(facing);
        }
        else if (delta > 1) {
            activeSprite = getFaceSprite();
            completeAttack();
            attacking = false;
        }
        else {
            activeSprite = getFaceSprite();
        }
    }
    
    public ColoredSprite getAttackSprite(DIRECTION dir) {
        switch(dir) {
            case RIGHT:
                return rightAttackSprite;
            case LEFT:
                return leftAttackSprite;
            case UP:
                return backAttackSprite;
            case DOWN:
                return frontAttackSprite;
            default:
                return frontAttackSprite;
        }
    }
    
    public DIRECTION getDirectionTowards(Mob other) {
        Point p2 = other.getCoords();
        Point p1 = this.getCoords();
        if (p1.x - p2.x == 1) {
            return DIRECTION.LEFT;
        }
        else if (p1.x - p2.x == -1) {
            return DIRECTION.RIGHT;
        }
        else if (p1.y - p2.y == -1) {
            return DIRECTION.DOWN;
        }
        else {
            return DIRECTION.UP;
        }
    }

    public MobResource getHealth() {
        return health;
    }

    public MobResource getStamina() {
        return stamina;
    }

    public MobResource getMana() {
        return mana;
    }

    public MobResource getAether() {
        return aether;
    }

    public int getAttackDamage() {
        return mobClass.getAttackBonus() + stats.getStrength().getModifier();
    }

    public void startCombatTurn() {
        combatTurnOver = false;
    }

    public void endCombatTurn() {
        combatTurnOver = true;
    }
    
    public boolean combatTurnOver() {
        return combatTurnOver;
    }

    public abstract boolean isFriendsWith(Mob other);

    /**
     * @return the doingAnim
     */
    public boolean isDoingAnim() {
        return doingAnim;
    }

    /**
     * @param doingAnim the doingAnim to set
     */
    public void setDoingAnim(boolean doingAnim) {
        this.doingAnim = doingAnim;
    }

    /**
     * @return the frontFaceSprite
     */
    public ColoredSprite getFrontFaceSprite() {
        return frontFaceSprite;
    }

    /**
     * @return the leftFaceSprite
     */
    public ColoredSprite getLeftFaceSprite() {
        return leftFaceSprite;
    }

    /**
     * @return the backFaceSprite
     */
    public ColoredSprite getBackFaceSprite() {
        return backFaceSprite;
    }

    /**
     * @return the rightFaceSprite
     */
    public ColoredSprite getRightFaceSprite() {
        return rightFaceSprite;
    }

    /**
     * @return the frontWalkSprite1
     */
    public ColoredSprite getFrontWalkSprite1() {
        return frontWalkSprite1;
    }

    /**
     * @return the frontWalkSprite2
     */
    public ColoredSprite getFrontWalkSprite2() {
        return frontWalkSprite2;
    }

    /**
     * @return the leftWalkSprite
     */
    public ColoredSprite getLeftWalkSprite() {
        return leftWalkSprite;
    }

    /**
     * @return the backWalkSprite1
     */
    public ColoredSprite getBackWalkSprite1() {
        return backWalkSprite1;
    }

    /**
     * @return the backWalkSprite2
     */
    public ColoredSprite getBackWalkSprite2() {
        return backWalkSprite2;
    }

    /**
     * @return the rightWalkSprite
     */
    public ColoredSprite getRightWalkSprite() {
        return rightWalkSprite;
    }

    /**
     * @return the lastFrontWalkSprite
     */
    public ColoredSprite getLastFrontWalkSprite() {
        return lastFrontWalkSprite;
    }

    /**
     * @return the lastBackWalkSprite
     */
    public ColoredSprite getLastBackWalkSprite() {
        return lastBackWalkSprite;
    }

    /**
     * @return the rightAttackSprite
     */
    public ColoredSprite getRightAttackSprite() {
        return rightAttackSprite;
    }

    /**
     * @return the leftAttackSprite
     */
    public ColoredSprite getLeftAttackSprite() {
        return leftAttackSprite;
    }

    /**
     * @return the backAttackSprite
     */
    public ColoredSprite getBackAttackSprite() {
        return backAttackSprite;
    }

    /**
     * @return the frontAttackSprite
     */
    public ColoredSprite getFrontAttackSprite() {
        return frontAttackSprite;
    }

    /**
     * @return the castSprite
     */
    public ColoredSprite getCastSprite() {
        return castSprite;
    }

    /**
     * @return the deadSprite
     */
    public ColoredSprite getDeadSprite() {
        return deadSprite;
    }

    /**
     * @return the activeSprite
     */
    public ColoredSprite getActiveSprite() {
        return activeSprite;
    }

    /**
     * @param activeSprite the activeSprite to set
     */
    public void setActiveSprite(ColoredSprite activeSprite) {
        this.activeSprite = activeSprite;
    }

    /**
     * @return the facing
     */
    public DIRECTION getFacing() {
        return facing;
    }

    public MobEquipSet getEquipSet() {
        return equipSet;
    }
    
    public static enum DIRECTION {

        UP,
        DOWN,
        LEFT,
        RIGHT
    }

    public static class MobSpriteSet {

        private ColoredSprite frontFaceSprite;
        private ColoredSprite leftFaceSprite;
        private ColoredSprite backFaceSprite;
        private ColoredSprite rightFaceSprite;

        private ColoredSprite frontWalkSprite1;
        private ColoredSprite frontWalkSprite2;
        private ColoredSprite leftWalkSprite;
        private ColoredSprite backWalkSprite1;
        private ColoredSprite backWalkSprite2;
        private ColoredSprite rightWalkSprite;
        
        private ColoredSprite rightAttackSprite;
        private ColoredSprite leftAttackSprite;
        private ColoredSprite backAttackSprite;
        private ColoredSprite frontAttackSprite;
        
        private ColoredSprite castSprite;
        private ColoredSprite deadSprite;

        public MobSpriteSet() {

        }

        public void setFrontFaceSprite(ColoredSprite sprite) {
            this.frontFaceSprite = sprite;
        }

        public ColoredSprite getLeftFaceSprite() {
            return leftFaceSprite;
        }

        public void setLeftFaceSprite(ColoredSprite leftFaceSprite) {
            this.leftFaceSprite = leftFaceSprite;
        }

        public ColoredSprite getBackFaceSprite() {
            return backFaceSprite;
        }

        public void setBackFaceSprite(ColoredSprite backFaceSprite) {
            this.backFaceSprite = backFaceSprite;
        }

        public ColoredSprite getRightFaceSprite() {
            return rightFaceSprite;
        }

        public void setRightFaceSprite(ColoredSprite rightFaceSprite) {
            this.rightFaceSprite = rightFaceSprite;
        }

        public ColoredSprite getFrontWalkSprite1() {
            return frontWalkSprite1;
        }

        public void setFrontWalkSprite1(ColoredSprite frontWalkSprite1) {
            this.frontWalkSprite1 = frontWalkSprite1;
        }

        public ColoredSprite getFrontWalkSprite2() {
            return frontWalkSprite2;
        }

        public void setFrontWalkSprite2(ColoredSprite frontWalkSprite2) {
            this.frontWalkSprite2 = frontWalkSprite2;
        }

        public ColoredSprite getLeftWalkSprite() {
            return leftWalkSprite;
        }

        public void setLeftWalkSprite(ColoredSprite leftWalkSprite) {
            this.leftWalkSprite = leftWalkSprite;
        }

        public ColoredSprite getBackWalkSprite1() {
            return backWalkSprite1;
        }

        public void setBackWalkSprite1(ColoredSprite backWalkSprite1) {
            this.backWalkSprite1 = backWalkSprite1;
        }

        public ColoredSprite getBackWalkSprite2() {
            return backWalkSprite2;
        }

        public void setBackWalkSprite2(ColoredSprite backWalkSprite2) {
            this.backWalkSprite2 = backWalkSprite2;
        }

        public ColoredSprite getRightWalkSprite() {
            return rightWalkSprite;
        }

        public void setRightWalkSprite(ColoredSprite rightWalkSprite) {
            this.rightWalkSprite = rightWalkSprite;
        }

        public ColoredSprite getFrontFaceSprite() {
            return frontFaceSprite;
        }

        /**
         * @return the rightAttackSprite
         */
        public ColoredSprite getRightAttackSprite() {
            return rightAttackSprite;
        }

        /**
         * @param rightAttackSprite the rightAttackSprite to set
         */
        public void setRightAttackSprite(ColoredSprite rightAttackSprite) {
            this.rightAttackSprite = rightAttackSprite;
        }

        /**
         * @return the leftAttackSprite
         */
        public ColoredSprite getLeftAttackSprite() {
            return leftAttackSprite;
        }

        /**
         * @param leftAttackSprite the leftAttackSprite to set
         */
        public void setLeftAttackSprite(ColoredSprite leftAttackSprite) {
            this.leftAttackSprite = leftAttackSprite;
        }

        /**
         * @return the backAttackSprite
         */
        public ColoredSprite getBackAttackSprite() {
            return backAttackSprite;
        }

        /**
         * @param backAttackSprite the backAttackSprite to set
         */
        public void setBackAttackSprite(ColoredSprite backAttackSprite) {
            this.backAttackSprite = backAttackSprite;
        }

        /**
         * @return the frontAttackSprite
         */
        public ColoredSprite getFrontAttackSprite() {
            return frontAttackSprite;
        }

        /**
         * @param frontAttackSprite the frontAttackSprite to set
         */
        public void setFrontAttackSprite(ColoredSprite frontAttackSprite) {
            this.frontAttackSprite = frontAttackSprite;
        }

        /**
         * @return the castSprite
         */
        public ColoredSprite getCastSprite() {
            return castSprite;
        }

        /**
         * @param castSprite the castSprite to set
         */
        public void setCastSprite(ColoredSprite castSprite) {
            this.castSprite = castSprite;
        }

        /**
         * @return the deadSprite
         */
        public ColoredSprite getDeadSprite() {
            return deadSprite;
        }

        /**
         * @param deadSprite the deadSprite to set
         */
        public void setDeadSprite(ColoredSprite deadSprite) {
            this.deadSprite = deadSprite;
        }
    }

    public static class MobResource {

        private final String name;
        private int maxValue;
        private int value;

        public MobResource(String name, int maxValue) {
            this.name = name;
            this.maxValue = maxValue;
            this.value = maxValue;
        }

        public void setValue(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }

        public void setMaxValue(int maxValue) {
            this.maxValue = maxValue;
        }

        public int getMaxValue() {
            return maxValue;
        }

        public String getName() {
            return name;
        }

        @Override
        public String toString() {
            return name + ": " + value + "/" + maxValue;
        }
    }

}
